package es.uji.viselab.sceneview;

import java.awt.GridLayout;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.media.j3d.AmbientLight;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Bounds;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.swing.JPanel;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import com.sun.j3d.loaders.Scene;
import com.sun.j3d.loaders.objectfile.ObjectFile;
import com.sun.j3d.utils.behaviors.mouse.MouseRotate;
import com.sun.j3d.utils.behaviors.mouse.MouseTranslate;
import com.sun.j3d.utils.behaviors.mouse.MouseZoom;
import com.sun.j3d.utils.universe.SimpleUniverse;

import es.uji.viselab.ViSeLabException;
import es.uji.viselab.math.Joints;
import es.uji.viselab.math.Vector3;
import es.uji.viselab.robot.Robot;
import es.uji.viselab.robot.RobotCatalog;
import es.uji.viselab.robot.RobotDatabase;
import es.uji.viselab.robot.RobotException;

/**
 * It is capable to load a 3D robot in a .obj file into a Java3D universe and
 * later to give it as a Canvas3D Be aware that the 3d object (in the .obj file)
 * must fit some restrictions to be transformed into a robot with joints
 * (=TransformGroups)
 * 
 * @author Roger Esteller-Curto
 * 
 */
public class VirtualRobotScene extends SceneView {

	public final static int MAX_AXES = 6;
	private SimpleUniverse universe;
	private Canvas3D canvas;
	RobotCatalog robotModel;

	// The axe 0 will be the scene root, that is why I need one axe more
	private VirtualRobotJoints[] joints = new VirtualRobotJoints[MAX_AXES + 1];

	/**
	 * Initializes a standard Java3D Universe that will contain the Canvas
	 * 
	 * @throws ViSeLabException
	 * @see #loadFile()
	 */
	public VirtualRobotScene(RobotCatalog robotModel) throws ViSeLabException {

		this.robotModel = robotModel;

		// Load the .obj file (3D model robot)
		Scene scene = loadFile(robotModel);
		// Each object of the scene must be conveniently ordered
		// (joints have parents and children)
		orderSceneObjects(scene);

		TransformGroup tg = new TransformGroup();
		BranchGroup root = new BranchGroup();

		// Grandfather --> Father --> Children
		// BranchGroup (root) --> TransformGroup (tg) --> Scene (scene)
		tg.addChild(scene.getSceneGroup());
		root.addChild(tg);

		// Adding some lights to the scene
		addLights(root);

		// add a mouse behaviour (so the entire universe will rotate using the
		// mouse)
		TransformGroup mouseGroup = createMouseBehaviorsGroup();
		BranchGroup root0 = new BranchGroup();

		// Grandfather --> Father --> Children
		// BG(root0) --> TransformGroup (mouseGroup) --> BG(root)
		mouseGroup.addChild(root);
		root0.addChild(mouseGroup);
		root0.compile();

		// look at the right spot
		Transform3D lookAt = new Transform3D();
		lookAt.lookAt(new Point3d(3, 3, 3), new Point3d(0.0, 0.0, 0.0), new Vector3d(0.0, 0.0, 1.0));
		lookAt.invert();

		// Create the canvas to draw in, and initialize our SimpleUniverse
		canvas = new Canvas3D(SimpleUniverse.getPreferredConfiguration());
		universe = new SimpleUniverse(canvas);

		universe.getViewingPlatform().getViewPlatformTransform().setTransform(lookAt);

		// Universe --> root0
		universe.addBranchGraph(root0);

		// END of the creation of the 3D universe

		// I susbcribe to the robot manager, so when the robot changes the
		// position, I will be able to change the robot 3D
		// RobotManager robotEventManager = RobotManager.getInstance();
		// robotEventManager.addListener(this);

	}

	public Canvas3D getCanvas() {
		return universe.getCanvas();
	}

	public Scene loadFile(RobotCatalog robotModel) throws ViSeLabException {

		String objFile = null;
		Scene scene = null;

		objFile = RobotDatabase.getFileNameRobot3D(robotModel);

		ObjectFile f = new ObjectFile();
		f.setFlags(ObjectFile.RESIZE | ObjectFile.TRIANGULATE | ObjectFile.STRIPIFY);

		try {
			scene = f.load(objFile);
		} catch (Exception e) {
			throw new ViSeLabException(
					"Could not load the obj file for the 3D robot. File not loaded: "
							+ System.getProperty("user.dir") + objFile, "VirtualRobot.loadFile()");
		}
		return scene;
	}

	private void addLights(BranchGroup objRoot) {

		DirectionalLight directionalLight = new DirectionalLight(new Color3f(0.5f, 0.5f, 0.5f),
				new Vector3f(-1.0f, -1.0f, -1.0f));
		directionalLight.setInfluencingBounds(new BoundingSphere(new Point3d(0.0d, 0.0d, 0.0d),
				100.0d));

		BoundingSphere bounds = new BoundingSphere(new Point3d(-5, -5, -5), 100.0);
		Color3f lightColor = new Color3f(1.0f, 1.0f, 1.0f);
		AmbientLight ambientLightNode = new AmbientLight(lightColor);
		ambientLightNode.setInfluencingBounds(bounds);

		objRoot.addChild(directionalLight);
		objRoot.addChild(ambientLightNode);
	}

	/**
	 * Pick ups all the objects of the scene and removes it, later place them
	 * again ordered. That order is based on the names of the objects. It puts
	 * also a TransforGroup between the joints, so they will capable to rotate
	 * 
	 * @param scene
	 */
	@SuppressWarnings("unchecked")
	private void orderSceneObjects(Scene scene) {

		BranchGroup root = scene.getSceneGroup();
		Hashtable<String, Shape3D> sceneHT;
		// Get all the objects of the scene by name
		sceneHT = scene.getNamedObjects();
		BoundingSphere bSphere;
		Point3d center = null;
		Boolean isAJoint = null;
		int jointId = 0;

		// The TransforGroup (joint) 0 is the root of the scene,
		// the rest 1...MAX_AXE+1 are the joints
		for (int i = 0; i < MAX_AXES + 1; i++) {
			joints[i] = new VirtualRobotJoints();
		}

		// I need to pick up all the objects from the scene in an precisse order
		Vector<String> objectsName = new Vector<String>(sceneHT.keySet());
		// Now the vector objectsNames, contains all the names
		// of the shapes ordered
		Collections.sort(objectsName);

		// All the shapes, should be removed and put again in
		// a correct and certain order
		for (Enumeration<String> e = objectsName.elements(); e.hasMoreElements();) {
			String key = (String) e.nextElement();
			Shape3D shape = sceneHT.get(key);
			root.removeChild(shape); // I remove from the main scene

			// All shapes, should have the name like "robot_joint1_000",
			// "robot_joint1_001"
			// only chapes with number 000 are joints
			if (key.contains("robot_joint") && key.contains("000")) {
				isAJoint = true;
				jointId = Integer.parseInt(key.substring(11, 12));
			} else
				isAJoint = false;

			if (isAJoint) {
				// I need to know where is the center of that object
				// That is because before rotating the joint I have to
				// translate it to the origin
				shape.setCapability(javax.media.j3d.Node.ALLOW_BOUNDS_READ);
				bSphere = new BoundingSphere(shape.getBounds());
				center = new Point3d();
				bSphere.getCenter(center);
				joints[jointId].setOrigin(center);

				// all the Transform Groups are chained to their parent, so when
				// a joint moves, all the others children, will move accordingly
				joints[jointId - 1].getTg().addChild(joints[jointId].getTg());
			}

			// The joint[0] do not exist in the robot, it is only the first
			// TransformGroup of the scene
			if (key.contains("robot_base"))
				joints[0].getTg().addChild(shape);
			if (key.contains("robot_joint1"))
				joints[1].getTg().addChild(shape);
			if (key.contains("robot_joint2"))
				joints[2].getTg().addChild(shape);
			if (key.contains("robot_joint3"))
				joints[3].getTg().addChild(shape);
			if (key.contains("robot_joint4"))
				joints[4].getTg().addChild(shape);
			if (key.contains("robot_joint5"))
				joints[5].getTg().addChild(shape);
			if (key.contains("robot_joint6"))
				joints[6].getTg().addChild(shape);
			if (key.contains("arrows_base"))
				joints[0].getTg().addChild(shape);
			if (key.contains("arrows_tool"))
				joints[6].getTg().addChild(shape);

		}

		// I put into the scene the root TransformGroup
		root.addChild(joints[0].getTg());

		// This is to indicate in which axes the joint rotates
		joints[1].setRotZ(1);
		joints[2].setRotY(1);
		joints[3].setRotY(1);
		joints[4].setRotX(1);
		joints[5].setRotY(1);
		joints[6].setRotX(1);

	}

	public void set(Joints j) {

		Long val;
		for (int i = 1; i <= MAX_AXES; i++) {
			val = (long) j.getJoint(i);
			joints[i].setDegrees(val);
		}

	}

	private TransformGroup createMouseBehaviorsGroup() {

		TransformGroup examineGroup = new TransformGroup();
		examineGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
		examineGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

		Bounds behaviorBounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 100.0);

		MouseRotate mr = new MouseRotate(examineGroup);
		mr.setSchedulingBounds(behaviorBounds);
		examineGroup.addChild(mr);

		MouseTranslate mt = new MouseTranslate(examineGroup);
		mt.setSchedulingBounds(behaviorBounds);
		examineGroup.addChild(mt);

		MouseZoom mz = new MouseZoom(examineGroup);
		mz.setSchedulingBounds(behaviorBounds);
		examineGroup.addChild(mz);

		return examineGroup;
	}

	@Override
	public void robotPositionChanged(Robot robot) {
		try {
			Joints joints = robot.getJoints();
			for (int i = 0; i < 6; i++)
				this.joints[i].setDegrees(joints.getJoint(i));
		} catch (RobotException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	@Override
	public void robotCreated(Robot robot) {
		// TODO Auto-generated method stub

	}

	@Override
	public void robotConnected(Robot robot) {
		// TODO Auto-generated method stub

	}

	@Override
	public void robotDisconnected() {
		// TODO Auto-generated method stub

	}

	@Override
	public JPanel getPanel() {
		JPanel panel = new JPanel();
		panel.setLayout(new GridLayout(1, 1, 2, 2));

		panel.add(getCanvas());
		return panel;
	}

	@Override
	public void plot(Vector3[] points) {
		// TODO Auto-generated method stub

	}

}
